goog.provide('dkpoints.ui.RaidMemberList');
goog.provide('dkpoints.ui.RaidMemberList.Event');
goog.provide('dkpoints.ui.RaidMemberList.EventType');


goog.require('dkpoints.models.Raider');
goog.require('dkpoints.models.Raider.CSSClass');
goog.require('dkpoints.models.Raider.Class');
goog.require('dkpoints.ui.templates.RaidMemberList');
goog.require('dkpoints.utils');

goog.require('goog.dom.TagName');
goog.require('goog.events');
goog.require('goog.events.Event');
goog.require('goog.events.KeyHandler');
goog.require('goog.positioning.AnchoredPosition');
goog.require('goog.positioning.Corner');
goog.require('goog.ui.Button');
goog.require('goog.ui.Component');
goog.require('goog.ui.ImagelessButtonRenderer');
goog.require('goog.ui.Popup');
goog.require('goog.ui.decorate');
goog.require('soy');



/**
 * A UI widget that encapsulates a list of raid members
 * (dkpoints.models.Raider objects).  It contains the functionality of adding
 * and removing players.  It communicates with the encapsulating view by
 * throwing custom events.
 * @param {!Array.<dkpoints.models.Raider>} raiders The list of players in the
 *    raid.
 * @param {boolean} editable Whether or not to show editing controls.
 * @param {goog.dom.DomHelper=} opt_dom The DomHelper for this document.
 * @constructor
 * @extends {goog.ui.Component}
 */
dkpoints.ui.RaidMemberList = function(raiders, editable, opt_dom) {
  dkpoints.utils.installCSS('raid', true);
  goog.base(this, opt_dom);

  /**
   * @type {Array.<dkpoints.models.Raider>}
   * @private
   */
  this.raiders_ = raiders;

  /**
   * @type {boolean}
   * @private
   */
  this.editable_ = editable;
};
goog.inherits(dkpoints.ui.RaidMemberList, goog.ui.Component);


/**
 * @inheritDoc
 */
dkpoints.ui.RaidMemberList.prototype.createDom = function() {
  var wrapperClass = goog.getCssName('raid_members_wrapper') +
          (this.editable_ ?
              ' ' + goog.getCssName('raid_members_wrapper_wide') :
              ''),
      el = this.dom_.createDom('div', wrapperClass),
      raiders = this.raiders_,
      playerData = [],
      renderData = {
        parentID: this.getId(),
        raidMembers: playerData,
        editable: this.editable_
      };

  for (var i = 0, len = raiders.length; i < len; ++i) {
    playerData[i] = {
      playerName: raiders[i].getName(),
      playerClass: dkpoints.models.Raider.CSSClass[raiders[i].getClass()]
    };
  }

  this.setElementInternal(el);
  el.innerHTML = dkpoints.ui.templates.RaidMemberList.render(renderData);
  el.id = this.getId();
};


/**
 * @inheritDoc
 */
dkpoints.ui.RaidMemberList.prototype.enterDocument = function() {
  goog.base(this, 'enterDocument');
  // In read-only mode, there are no events worth listening for.
  if (this.editable_) {
    // Register a top-level event handler to listen for button clicks.
    // A button click is either a remove or add command.
    var handler = this.getHandler(),
        el = this.getElement(),
        newNameInput = goog.dom.getElement(this.getId() + '.new_raider'),
        keyHandler = this.keyHandler_;

    if (! keyHandler) {
      /**
       * @type {goog.events.KeyHandler}
       * @private
       */
      keyHandler = this.keyHandler_ = new goog.events.KeyHandler(newNameInput);
    }
    handler.listen(keyHandler, goog.events.KeyHandler.EventType.KEY,
                   this.handleKeyPress_);

    // Listen for all click events thrown by child components (the buttons).
    handler.listen(this, goog.ui.Component.EventType.ACTION,
                   this.buttonAction_);

    var buttonParents = this.getElement().
            getElementsByClassName('member_option'),
        fancyButtons = [],
        renderer = goog.ui.ImagelessButtonRenderer.getInstance(),
        dom = this.dom_;

    for (var i = 0, len = buttonParents.length; i < len; ++i) {
      var button = buttonParents[i].firstChild,
          fancyButton = fancyButtons[i] = new goog.ui.Button('', renderer, dom);

      fancyButton.setId(button.id);
      this.addChild(fancyButton);
      fancyButton.decorate(button);
    }

    /**
     * @type {Array.<goog.ui.Button>}
     * @private
     */
    this.buttons_ = fancyButtons;
  }
};


/**
 * Event handler for click events on either the Remove or Add Raider buttons.
 * @param {goog.events.Event} e The click event.
 * @private
 */
dkpoints.ui.RaidMemberList.prototype.buttonAction_ = function(e) {
  var listID = this.getId(),
      target = e.target,
      buttonID = target.getId(),
      actionIndex = this.getId().length + 1,
      action = buttonID.substr(actionIndex, 3); // "add" or "rmv"

  if (action == 'add') {
    var addNameInput = goog.dom.getElement(listID + '.new_raider'),
        raiderToAdd = addNameInput.value;

    if (raiderToAdd) {
      // Ignore the button if it's clicked with an empty input box.
      if (dkpoints.models.Raider.isValidName(raiderToAdd)) {
        this.addRaider_(raiderToAdd);
        addNameInput.value = '';
      }
      else {
        this.displayNameError_('"' + raiderToAdd + '"' +
                               ' is not a valid character name.');
      }
      addNameInput.focus();
    }
  }
  else if (action == 'rmv') {
    var raiderToRemove = buttonID.substr(actionIndex + 3);
    this.removeRaider_(raiderToRemove, /** @type {goog.ui.Button} */ (target));
  }
};


/**
 * Handle keypress events on the add new raider input box.  Listen for:
 *   - Space key: Show popup that space is an invalid character.
 *   - Enter key: call addRaider_ and clear the input box.
 * @param {goog.events.KeyEvent} e The key event.
 * @private
 */
dkpoints.ui.RaidMemberList.prototype.handleKeyPress_ = function(e) {
  var target = e.target,
      name = target.value;

  if (e.keyCode == goog.events.KeyCodes.ENTER && name !== '') {
    if (dkpoints.models.Raider.isValidName(name)) {
      this.addRaider_(name);
      target.value = '';
      target.focus();
    }
    else {
      this.displayNameError_('"' + name + '"' +
                             ' is not a valid character name.');
      e.preventDefault();
    }
  }
  else if (e.keyCode == goog.events.KeyCodes.SPACE) {
    this.displayNameError_('Character names cannot include spaces.');
    e.preventDefault();
  }
};


/**
 * Show a popup under the new raider name input with the given message.
 * @param {string} message The message to show.
 * @private
 */
dkpoints.ui.RaidMemberList.prototype.displayNameError_ = function(message) {
  var popup = this.newRaiderPopup_,
      el;

  if (! popup) {
    var addNameInput = goog.dom.getElement(this.getId() + '.new_raider'),
        position =
        new goog.positioning.AnchoredPosition(
            addNameInput, goog.positioning.Corner.BOTTOM_LEFT);
    el =
        this.dom_.createDom('div', goog.getCssName('invalid_name_popup'));
    document.body.appendChild(el);

    /**
     * @type {goog.ui.Popup}
     * @private
     */
    this.newRaiderPopup_ = popup = new goog.ui.Popup(el, position);
    popup.setHideOnEscape(true);
  }
  el = el || popup.getElement();
  goog.dom.setTextContent(el, message);
  popup.setVisible(true);
};


/**
 * @param {string} raiderName The name of the raider to add to the raid members
 *    list.
 * @private
 */
dkpoints.ui.RaidMemberList.prototype.addRaider_ = function(raiderName) {
  var raiders = this.raiders_;
  for (var i = 0, len = raiders.length; i < len; ++i) {
    if (raiderName === raiders[i].getName()) {
      this.displayNameError_(raiderName + ' is already in the raid.');
      return;
    }
  }

  var names = this.getElement().getElementsByClassName('playername'),
      number = names.length,
      raiderList = names[0].parentNode.parentNode,
      templateData = {
        playerName: raiderName,
        playerClass: dkpoints.models.Raider.CSSClass['noclass'],
        number: number,
        editable: true,
        parentID: this.getId()
      };

  var newEl =
      soy.renderAsFragment(dkpoints.ui.templates.RaidMemberList.newRaider,
                           templateData, this.dom_.getDocument());

  raiderList.insertBefore(newEl, names[number - 1].parentNode);
  var buttonEl =
      newEl.getElementsByClassName(goog.getCssName('goog-imageless-button'))[0];

  var fancyButton =
      new goog.ui.Button('', goog.ui.ImagelessButtonRenderer.getInstance(),
                         this.dom_);

  var buttons = this.buttons_,
      index = buttons.length - 1;
  buttons.splice(index, 0, fancyButton);

  this.addChildAt(fancyButton, index);
  fancyButton.decorate(buttonEl);
  fancyButton.setId(buttonEl.id);

  // TODO: Create system to query server for player class.
  var raider =
      new dkpoints.models.Raider(raiderName, dkpoints.models.Raider.Class.NONE);
  raiders.push(raider);

  var type = dkpoints.ui.RaidMemberList.EventType.ADDED,
      addEvent = new dkpoints.ui.RaidMemberList.Event(type, raiderName);

  this.dispatchEvent(addEvent);
};


/**
 * Remove the raider from the list.
 * @param {string} raiderName The name of the raider to remove from the list.
 * @param {goog.ui.Button} button The button that was clicked.
 * @private
 */
dkpoints.ui.RaidMemberList.prototype.removeRaider_ =
    function(raiderName, button) {

  var names = this.getElement().getElementsByClassName('playername'),
      number = names.length,
      raiderList = names[0].parentNode.parentNode;

  // Search for the raider in the list of players.
  var raiders = this.raiders_,
      i = 0,
      len = raiders.length;
  for (; i < len; ++i) {
    if (raiderName === raiders[i].getName()) {
      break;
    }
  }

  if (i === len) {
    throw Error(raiderName + ' is not in the raid.');
  }

  if (goog.dom.getTextContent(names[i].children[0]) == raiderName) {
    var parent = names[i].parentNode,
        nextRaider = parent.nextSibling;
    len = names.length;

    parent.parentNode.removeChild(parent);
    this.removeChild(button);
    button.dispose();

    this.buttons_.splice(i, 1);
    raiders.splice(i, 1);

    while (i < len - 2) {
      goog.dom.setTextContent(names[i].firstChild, (++i) + '. ');
      nextRaider = nextRaider.nextSibling;
    }

    var type = dkpoints.ui.RaidMemberList.EventType.REMOVED,
        rmvEvent = new dkpoints.ui.RaidMemberList.Event(type, raiderName);

    this.dispatchEvent(rmvEvent);
  }
  else {
    throw Error('Raider list out of sync.  Cound not remove raider: ' +
                raiderName);
  }
};


/**
 * @inheritDoc
 */
dkpoints.ui.RaidMemberList.prototype.disposeInternal = function() {
  goog.base(this, 'disposeInternal');

  var popup = this.newRaiderPopup_;
  if (popup) {
    var el = popup.getElement();
    popup.dispose();
    if (el.parentNode) {
      el.parentNode.removeChild(el);
    }
  }
  this.newRaiderPopup_ = null;

  var keyHandler = this.keyHandler_;
  if (keyHandler) {
    keyHandler.dispose();
  }
  this.keyHandler_ = null;

  var buttons = this.buttons_;
  if (buttons) {
    for (var i = 0, len = buttons.length; i < len; ++i) {
      buttons[i].dispose();
    }
  }

  this.raiders_ = null;
};



/**
 * An event to encapsulating either the addition or removal of a raider from
 * the raid.  This is propagated up to whomever is this component's parent.
 * @param {dkpoints.ui.RaidMemberList.EventType} type The event type.
 * @param {string} name The name of the player to remove.
 * @constructor
 * @extends {goog.events.Event}
 */
dkpoints.ui.RaidMemberList.Event = function(type, name) {
  /**
   * Event type.
   * @type {string}
   */
  this.type = type;

  /**
   * Player to be added or removed.
   * @type {string}
   */
  this.name = name;
};
goog.inherits(dkpoints.ui.RaidMemberList.Event, goog.events.Event);


/**
 * The event type enum for RaidMemberList events.
 * @enum {string}
 */
dkpoints.ui.RaidMemberList.EventType = {
  ADDED: goog.events.getUniqueId('added'),
  REMOVED: goog.events.getUniqueId('removed')
};
